# load required libraries

# to use harry potter dataset
# devtools::install_github("bradleyboehmke/harrypotter")
# devtools::install_github("quanteda/quanteda.sentiment")
# devtools::install_github("quanteda/quanteda.corpora")

library(quanteda)
library(readtext)
library(corpus)
library(tidyverse)
library(stringr)
library(tidytext)
library(harrypotter)
library(dplyr)
library(quanteda.sentiment)
library(vader)
library(caret)
library(reshape2)


require(quanteda)
require(quanteda.corpora)
require(quanteda.sentiment)
#library("quanteda", warn.conflicts = FALSE, quietly = TRUE)

1. Step: Load Data & Lexicons

# load datasets
reviews <- readRDS(file="datasets/red_review.rds")
twitter <- readRDS(file="datasets/red_twitter.rds")
parlvote <- readRDS(file="datasets/red_parl_vote.rds")

# load lexicon
afinn <- data_dictionary_AFINN
reviews
twitter
parlvote

2. Step: Perform Sentiment Analysis

Optional Step: Normalize Lexicon Scores

# min/max normalization from 0 to 1
normalize <- function(x, na.rm = TRUE){
  return((x-min(x)) / (max(x)-min(x)))
}

# min/max normalization from -1 to 1
normalize2 <- function(x){
  return(2* ((x - min(x)) / (max(x)-min(x)))-1)
  }

parlvote_sentiment$afinn_norm <- normalize2(parlvote_sentiment$afinn)
parlvote_sentiment$lsd_norm <- normalize2(parlvote_sentiment$lsd)
parlvote_sentiment$vader_norm <- normalize2(parlvote_sentiment$vader)

3. Step: Calculate Statistics

# convert to binary results
get_binary <- function(dataframes, type){
  for(df in dataframes){
    df$afinn_binary[df$afinn > 0] <- "pos"
    df$afinn_binary[df$afinn <= 0] <- "neg"
        
    df$lsd_binary[df$lsd > 0] <- "pos"
    df$lsd_binary[df$lsd <= 0] <- "neg"
        
    df$vader_binary[df$vader > 0] <- "pos"
    df$vader_binary[df$vader <= 0] <- "neg"
        
    df$rating_binary[df$rating >= 3] <- "pos"
    df$rating_binary[df$rating < 3] <- "neg"
    if(type == "01"){
      df[df == "pos"] <- 1
      df[df == "neg"] <- 0
    }
  }
  return(dataframes)
}


x <- get_binary(list(reviews_sentiment, twitter_sentiment, parlvote_sentiment), "01")
reviews_sentiment$afinn_binary[reviews_sentiment$afinn > 0] <- "1"
reviews_sentiment$afinn_binary[reviews_sentiment$afinn <= 0] <- "0"
reviews_sentiment$lsd_binary[reviews_sentiment$lsd > 0] <- "1"
reviews_sentiment$lsd_binary[reviews_sentiment$lsd <= 0] <- "0"
reviews_sentiment$vader_binary[reviews_sentiment$vader > 0] <- "1"
reviews_sentiment$vader_binary[reviews_sentiment$vader <= 0] <- "0"
reviews_sentiment$rating_binary[reviews_sentiment$rating >= 3] <- "1"
reviews_sentiment$rating_binary[reviews_sentiment$rating < 3] <- "0"

twitter_sentiment$afinn_binary[twitter_sentiment$afinn > 0] <- "1"
twitter_sentiment$afinn_binary[twitter_sentiment$afinn <= 0] <- "0"
twitter_sentiment$lsd_binary[twitter_sentiment$lsd > 0] <- "1"
twitter_sentiment$lsd_binary[twitter_sentiment$lsd <= 0] <- "0"
twitter_sentiment$vader_binary[twitter_sentiment$vader > 0] <- "1"
twitter_sentiment$vader_binary[twitter_sentiment$vader <= 0] <- "0"
twitter_sentiment$rating_binary[twitter_sentiment$rating == "Positive"] <- "1"
twitter_sentiment$rating_binary[twitter_sentiment$rating == "Negative"] <- "0"
twitter_sentiment$rating_binary[twitter_sentiment$rating == "Neutral"] <- NA

parlvote_sentiment$afinn_binary[parlvote_sentiment$afinn > 0] <- "1"
parlvote_sentiment$afinn_binary[parlvote_sentiment$afinn <= 0] <- "0"
parlvote_sentiment$lsd_binary[parlvote_sentiment$lsd > 0] <- "1"
parlvote_sentiment$lsd_binary[parlvote_sentiment$lsd <= 0] <- "0"
parlvote_sentiment$vader_binary[parlvote_sentiment$vader > 0] <- "1"
parlvote_sentiment$vader_binary[parlvote_sentiment$vader <= 0] <- "0"

# optionally: convert 0 = negative, 1 = positive
#reviews_50[reviews_50 == "pos"] <- 1
#reviews_50[reviews_50 == "neg"] <- 0
get_statistics <- function(df) {
  #statistics <- data.frame(matrix(ncol=4, nrow=0))
  #x <- c("accuracy", "precision", "recall", "F1")
  #colnames(statistics) <- x
  statistics <- data.frame(matrix(ncol=1, nrow=0))
  colnames(statistics) <- "accuracy"
  
  lex1 <- "afinn_binary"
  lex2 <- "lsd_binary"
  lex3 <- "vader_binary"
  gold <- "rating_binary"
  
  lexicons <- c(lex1,lex2,lex3)
  
  for(lex in lexicons){
    cf <- confusionMatrix(factor(df[[lex]]),factor(df[[gold]]))
    accuracy <- cf[[3]][1]
    #confusion_matrix <- table(ACTUAL=df[[gold]], PREDICTED=df[[lex]])
    #TN <- confusion_matrix[1]
    #FN <- confusion_matrix[2]
    #FP <- confusion_matrix[3]
    #TP <- confusion_matrix[4]
    
    # calculate statistics
    #precision <- TP/(TP+FP)
    #accuracy <- (TP+TN)/(TP+TN+FP+FN)
    #recall <- TP/(TP+FN)
    #F1 <- (2*precision*recall)/(precision+recall)
  
    # add to table
    #output <- c(accuracy,precision, recall, F1)
    #statistics[lex,] = rbind(statistics[[lex]], output)
    statistics[lex,] = rbind(statistics[[lex]], accuracy)
    #print(output)
    }
    
  return(statistics)
  
}

review_stats <- get_statistics(reviews_sentiment)
#twitter_stats <- get_statistics(twitter_sentiment)
#parlvote_stats <- get_statistics(parlvote_sentiment)

review_stats

4. Step: Plot Data

ADDITIONAL

Harry Potter - Dataset

# load harry potter dataset 
titles <- c("Philosopher's Stone", "Chamber of Secrets", "Prisoner of Azkaban",
            "Goblet of Fire", "Order of the Phoenix", "Half-Blood Prince",
            "Deathly Hallows")

books <- list(philosophers_stone, chamber_of_secrets, prisoner_of_azkaban,
           goblet_of_fire, order_of_the_phoenix, half_blood_prince,
           deathly_hallows)
  
series <- tibble()

for(i in seq_along(titles)) {
        
        clean <- tibble(chapter = seq_along(books[[i]]),
                        text = books[[i]]) %>%
             #unnest_tokens(word, text) %>%
             mutate(book = titles[i]) %>%
             select(book, everything())

        series <- rbind(series, clean)
}

series$book <- factor(series$book, levels = rev(titles))

series
#book_groups <- series %>% group_by(book, chapter)

Harry Potter - AFINN Lexicon

afinn_hp2 <- series %>%
        group_by(book, chapter) %>% # add word for single word scores 
        inner_join(get_sentiments("afinn")) %>%
        group_by(book, chapter) %>% # add word for single word scores
        #summarise(sentiment = sum(value)) %>%
        summarise(sentiment = mean(value, na.rm = TRUE)) %>%
        mutate(method = "AFINN")  %>%
        ggplot(aes(chapter, sentiment, fill = book)) +
          geom_bar(alpha = 0.8, stat = "identity", show.legend = FALSE) +
          facet_wrap(~ book, ncol = 2, scales = "free_x")

afinn_hp2

#ggsave(plot = afinn, width = 15, height = 15, dpi = 300, filename = "afinn_hp_mean.png")

Lexicoder: HP


hp1_lsd.df <- as.data.frame.matrix(hp1_lsd)

hp1_lsd.df$chapter <- 1:nrow(hp1_lsd.df)

plot <- ggplot(hp1_lsd, aes(x =hp1_lsd.df$chapter, y=sentiment)) +
          geom_bar(alpha = 0.8, stat = "identity", show.legend = FALSE)
plot + ylim(-1.0, 1.0) + labs(y="sentiment", x = "chapter") + ggtitle("HP1 - Lexicoder")


#hp1_lsd.df

AFINN: HP

plot + ylim(-1.0, 1.0) + labs(y="sentiment", x = "chapter") + ggtitle("HP1 - AFINN")
Warnung: Use of `hp1_afinn.df$chapter` is discouraged. Use `chapter` instead.

VADER: HP

QUANTEDA.SENTIMENT

AFINN: HP

# Work with quanteda.sentiment on HP corpus:

# convert tibble to dataframe
series.df <- as.data.frame(series)

# tokenize books
series_tokenized <- series.df %>%
  unnest_tokens(tokens, text)

# apply afinn lexicon
series_tokenized$afinn2 <- textstat_valence(series_tokenized$tokens, afinn2)$sentiment

# replace all 0 values with na
series_tokenized[series_tokenized == 0] <- NA

series_tokenized %>%
  group_by(book, chapter) %>% # group df by book and chapter to get sentiment per chapter
  summarise(sentiment = mean(afinn2, na.rm = TRUE)) %>% # calculate mean w/o regarding na values
  mutate(method = "AFINN") %>% # add column with method 
        ggplot(aes(chapter, sentiment, fill = book)) + # plot sentiment of books
          geom_bar(alpha = 0.8, stat = "identity", show.legend = FALSE) +
          facet_wrap(~ book, ncol = 2, scales = "free_x") +
          ggtitle("AFINN HP")
`summarise()` has grouped output by 'book'. You can override using the `.groups` argument.

Lexicoder: HP

# Work with quanteda.sentiment on HP corpus:

# apply lexicoder lexicon
series$lsd <- textstat_polarity(tokens(series$text), data_dictionary_LSD2015)$sentiment 

#series.df <- as.data.frame(series)

plot <- ggplot(series, aes(chapter, lsd, fill = book)) + # plot sentiment of books
          geom_bar(alpha = 0.8, stat = "identity", show.legend = FALSE) +
          facet_wrap(~ book, ncol = 2, scales = "free_x") +
          ggtitle("Lexicoder HP")
plot 

Vader: HP

REVIEWS DATASET

# load dataset
reviews <- readtext("datasets/goodreads_reviews_children_2.json", text_field = "review_text")

# convert to dataframe
reviews.df <- as.data.frame(reviews)

# add doc_id (i.e. according to index)
reviews.df$doc_id <- 1:nrow(reviews.df)

Sample Dataset

# get random sample of 50 reviews 
reviews_sample <- reviews.df[sample(1:nrow(reviews.df), 50,
   replace=FALSE),]

# get first 50 rows of data 
reviews_50 <- head(reviews.df,50)
reviews_50 = subset(reviews_50, select = c(doc_id,text,rating))
reviews_50

Get Translations of Dataset

# either via corpus 
reviews.corpus <- corpus(reviews)
docvars(reviews.corpus, "language") <- textcat(reviews.corpus)
reviews_en <- corpus_subset(reviews.corpus, language == "english", drop_docid = TRUE)

# or via dataframe logic
reviews.df$language <- textcat(reviews.df$text)

Sentiment Analysis on Reviews Dataset

AFINN

# Afinn
# tokenize 
reviews_tokenized <- reviews_50 %>%
  unnest_tokens(tokens, text)

# apply afinn lexicon
reviews_tokenized$afinn2 <- textstat_valence(reviews_tokenized$tokens, afinn2)$sentiment

# replace all 0 values with na
reviews_tokenized[reviews_tokenized == 0] <- NA

# calculate mean scores for tokens per doc
afinn_scores <- reviews_tokenized %>%
  group_by(doc_id) %>% # group df by doc_id to get mean sentiment score
  summarise(total = mean(afinn2, na.rm = TRUE)) #%>% # calculate mean w/o regarding na values

# add afinn scores to df 
reviews_50$afinn <- afinn_scores$total

# different version to get plot 
reviews_tokenized %>%
  group_by(doc_id) %>% # group df by book and chapter to get sentiment per chapter
  #reviews_tokenized$sentiment = mean(afinn2, na.rm = TRUE) %>%
  summarise(sentiment = mean(afinn2, na.rm = TRUE)) %>% # calculate mean w/o regarding na values
  mutate(method = "AFINN") %>% # add column with method 
        ggplot(aes(doc_id, sentiment, fill = doc_id)) + # plot sentiment of books
          geom_bar(alpha = 0.8, stat = "identity", show.legend = FALSE) +
          #facet_wrap(~ doc_id, ncol = 2, scales = "free_x") +
          ggtitle("AFINN Reviews")
Warnung: Removed 1 rows containing missing values (position_stack).

LEXICODER

# apply lexicoder lexicon
reviews_50$lsd <- textstat_polarity(tokens(reviews_50$text), data_dictionary_LSD2015)$sentiment 
#series.df <- as.data.frame(series)

plot <- ggplot(reviews_50, aes(doc_id, lsd, fill = doc_id)) + # plot sentiment of books
          geom_bar(alpha = 0.8, stat = "identity", show.legend = FALSE) +
          #facet_wrap(~ doc_id, ncol = 2, scales = "free_x") +
          ggtitle("Lexicoder Reviews")
plot 

Vader

Statistics

# convert to binary results
reviews_50$afinn_binary[reviews_50$afinn > 0] <- "pos"
reviews_50$afinn_binary[reviews_50$afinn <= 0] <- "neg"

reviews_50$lsd_binary[reviews_50$lsd > 0] <- "pos"
reviews_50$lsd_binary[reviews_50$lsd <= 0] <- "neg"

reviews_50$vader_binary[reviews_50$vader > 0] <- "pos"
reviews_50$vader_binary[reviews_50$vader <= 0] <- "neg"

reviews_50$rating_binary[reviews_50$rating >= 3] <- "pos"
reviews_50$rating_binary[reviews_50$rating < 3] <- "neg"

# optionally: convert 0 = negative, 1 = positive
reviews_50[reviews_50 == "pos"] <- 1
reviews_50[reviews_50 == "neg"] <- 0
actual_values <- twitter_sentiment$rating_binary
predict_values <- twitter_sentiment$afinn_binary

# create confusion matrix 
confusion_matrix <- table(ACTUAL=actual_values, PREDICTED=predict_values)

# assign values from matrix to true/false positives/negatives
TN <- confusion_matrix[1]
FN <- confusion_matrix[2]
FP <- confusion_matrix[3]
TP <- confusion_matrix[4]

# calculate statistics
precision <- TP/(TP+FP)
accuracy <- (TP+TN)/(TP+TN+FP+FN)
recall <- TP/(TP+FN)
F1 <- (2*precision*recall)/(precision+recall)

precision
[1] NA
accuracy
[1] NA
recall
[1] NA
confusion_matrix[3]
[1] NA
get_statistics <- function(df) {
  statistics <- data.frame(matrix(ncol=4, nrow=0))
  x <- c("accuracy", "precision", "recall", "F1")
  colnames(statistics) <- x
  lex1 <- "afinn_binary"
  lex2 <- "lsd_binary"
  lex3 <- "vader_binary"
  gold <- "rating_binary"
  
  lexicons <- c(lex1,lex2,lex3)
  
  for(lex in lexicons){
    confusion_matrix <- table(ACTUAL=df[[gold]], PREDICTED=df[[lex]])
    TN <- confusion_matrix[1]
    FN <- confusion_matrix[2]
    FP <- confusion_matrix[3]
    TP <- confusion_matrix[4]
  
    # calculate statistics
    precision <- TP/(TP+FP)
    accuracy <- (TP+TN)/(TP+TN+FP+FN)
    recall <- TP/(TP+FN)
    F1 <- (2*precision*recall)/(precision+recall)
  
    # add to table
    output <- c(accuracy,precision, recall, F1)
    statistics[lex,] = rbind(statistics[[lex]], output)
    }
    
  return(statistics)
  
}

get_statistics(test)
LS0tCnRpdGxlOiAiQ29tcGFyaXNvbiBvZiBTZW50aW1lbnQgVG9vbHMgYWNyb3NzIERvbWFpbnMiCm91dHB1dDogaHRtbF9ub3RlYm9vawotLS0KYGBge3J9CiMgbG9hZCByZXF1aXJlZCBsaWJyYXJpZXMKCiMgdG8gdXNlIGhhcnJ5IHBvdHRlciBkYXRhc2V0CiMgZGV2dG9vbHM6Omluc3RhbGxfZ2l0aHViKCJicmFkbGV5Ym9laG1rZS9oYXJyeXBvdHRlciIpCiMgZGV2dG9vbHM6Omluc3RhbGxfZ2l0aHViKCJxdWFudGVkYS9xdWFudGVkYS5zZW50aW1lbnQiKQojIGRldnRvb2xzOjppbnN0YWxsX2dpdGh1YigicXVhbnRlZGEvcXVhbnRlZGEuY29ycG9yYSIpCgpsaWJyYXJ5KHF1YW50ZWRhKQpsaWJyYXJ5KHJlYWR0ZXh0KQpsaWJyYXJ5KGNvcnB1cykKbGlicmFyeSh0aWR5dmVyc2UpCmxpYnJhcnkoc3RyaW5ncikKbGlicmFyeSh0aWR5dGV4dCkKbGlicmFyeShoYXJyeXBvdHRlcikKbGlicmFyeShkcGx5cikKbGlicmFyeShxdWFudGVkYS5zZW50aW1lbnQpCmxpYnJhcnkodmFkZXIpCmxpYnJhcnkoY2FyZXQpCmxpYnJhcnkocmVzaGFwZTIpCgoKcmVxdWlyZShxdWFudGVkYSkKcmVxdWlyZShxdWFudGVkYS5jb3Jwb3JhKQpyZXF1aXJlKHF1YW50ZWRhLnNlbnRpbWVudCkKI2xpYnJhcnkoInF1YW50ZWRhIiwgd2Fybi5jb25mbGljdHMgPSBGQUxTRSwgcXVpZXRseSA9IFRSVUUpCmBgYAoKIyAxLiBTdGVwOiBMb2FkIERhdGEgJiBMZXhpY29ucwpgYGB7cn0KIyBsb2FkIGRhdGFzZXRzCnJldmlld3MgPC0gcmVhZFJEUyhmaWxlPSJkYXRhc2V0cy9yZWRfcmV2aWV3LnJkcyIpCnR3aXR0ZXIgPC0gcmVhZFJEUyhmaWxlPSJkYXRhc2V0cy9yZWRfdHdpdHRlci5yZHMiKQpwYXJsdm90ZSA8LSByZWFkUkRTKGZpbGU9ImRhdGFzZXRzL3JlZF9wYXJsX3ZvdGUucmRzIikKCiMgbG9hZCBsZXhpY29uCmFmaW5uIDwtIGRhdGFfZGljdGlvbmFyeV9BRklOTgpgYGAKCmBgYHtyfQpyZXZpZXdzCnR3aXR0ZXIKcGFybHZvdGUKYGBgCiMgMi4gU3RlcDogUGVyZm9ybSBTZW50aW1lbnQgQW5hbHlzaXMKYGBge3J9CiMgY29tcGxldGUgZnVuY3Rpb24gdG8gZ2V0IGxleGljb24gc2NvcmVzCmdldF9zZW50aW1lbnQgPC0gZnVuY3Rpb24oZGF0YSwgbGV4aWNvbnMpewogIAogIGZvcihsZXggaW4gbGV4aWNvbnMpewogICAgaWYobGV4ID09ICJhZmlubiIpewogICAgICAKICAgIGRhdGEkYWZpbm4gPC0gdGV4dHN0YXRfdmFsZW5jZSh0b2tlbnMoZGF0YSR0ZXh0KSwgYWZpbm4sIG5vcm1hbGl6ZT0iZGljdGlvbmFyeSIpJHNlbnRpbWVudAogICAgfQogICAgCiAgICBpZihsZXggPT0gImxzZCIpewogICAgZGF0YSRsc2Q8LSB0ZXh0c3RhdF9wb2xhcml0eSh0b2tlbnMoZGF0YSR0ZXh0KSwgZGF0YV9kaWN0aW9uYXJ5X0xTRDIwMTUpJHNlbnRpbWVudAogICAgfQogICAgCiAgICBpZihsZXggPT0gInZhZGVyIil7CiAgICBkYXRhJHZhZGVyIDwtIHZhZGVyX2RmKGRhdGEkdGV4dCkkY29tcG91bmQKICAgIH0KICB9CiAgZGF0YVtpcy5uYShkYXRhKV0gPC0gMAogIHJldHVybihkYXRhKQp9CgpyZXZpZXdzX3NlbnRpbWVudCA8LSBnZXRfc2VudGltZW50KHJldmlld3MsIGMoImFmaW5uIiwibHNkIiwgInZhZGVyIikpCnR3aXR0ZXJfc2VudGltZW50IDwtIGdldF9zZW50aW1lbnQodHdpdHRlciwgYygiYWZpbm4iLCJsc2QiLCAidmFkZXIiKSkKcGFybHZvdGVfc2VudGltZW50IDwtIGdldF9zZW50aW1lbnQocGFybHZvdGUsIGMoImFmaW5uIiwibHNkIiwgInZhZGVyIikpCgpwYXJsdm90ZV9zZW50aW1lbnQKI3dyaXRlLmNzdihwYXJsdm90ZV9zZW50aW1lbnQsICJkYXRhc2V0cy9wYXJsdm90ZV9zZW50aW1lbnQuY3N2Iiwgcm93Lm5hbWVzID0gRkFMU0UpCmBgYAojIE9wdGlvbmFsIFN0ZXA6IE5vcm1hbGl6ZSBMZXhpY29uIFNjb3JlcwpgYGB7cn0KIyBtaW4vbWF4IG5vcm1hbGl6YXRpb24gZnJvbSAwIHRvIDEKbm9ybWFsaXplIDwtIGZ1bmN0aW9uKHgsIG5hLnJtID0gVFJVRSl7CiAgcmV0dXJuKCh4LW1pbih4KSkgLyAobWF4KHgpLW1pbih4KSkpCn0KCiMgbWluL21heCBub3JtYWxpemF0aW9uIGZyb20gLTEgdG8gMQpub3JtYWxpemUyIDwtIGZ1bmN0aW9uKHgpewogIHJldHVybigyKiAoKHggLSBtaW4oeCkpIC8gKG1heCh4KS1taW4oeCkpKS0xKQogIH0KCnBhcmx2b3RlX3NlbnRpbWVudCRhZmlubl9ub3JtIDwtIG5vcm1hbGl6ZTIocGFybHZvdGVfc2VudGltZW50JGFmaW5uKQpwYXJsdm90ZV9zZW50aW1lbnQkbHNkX25vcm0gPC0gbm9ybWFsaXplMihwYXJsdm90ZV9zZW50aW1lbnQkbHNkKQpwYXJsdm90ZV9zZW50aW1lbnQkdmFkZXJfbm9ybSA8LSBub3JtYWxpemUyKHBhcmx2b3RlX3NlbnRpbWVudCR2YWRlcikKYGBgCgoKIyAzLiBTdGVwOiBDYWxjdWxhdGUgU3RhdGlzdGljcwpgYGB7cn0KIyBjb252ZXJ0IHRvIGJpbmFyeSByZXN1bHRzCmdldF9iaW5hcnkgPC0gZnVuY3Rpb24oZGF0YWZyYW1lcywgdHlwZSl7CiAgZm9yKGRmIGluIGRhdGFmcmFtZXMpewogICAgZGYkYWZpbm5fYmluYXJ5W2RmJGFmaW5uID4gMF0gPC0gInBvcyIKICAgIGRmJGFmaW5uX2JpbmFyeVtkZiRhZmlubiA8PSAwXSA8LSAibmVnIgogICAgICAgIAogICAgZGYkbHNkX2JpbmFyeVtkZiRsc2QgPiAwXSA8LSAicG9zIgogICAgZGYkbHNkX2JpbmFyeVtkZiRsc2QgPD0gMF0gPC0gIm5lZyIKICAgICAgICAKICAgIGRmJHZhZGVyX2JpbmFyeVtkZiR2YWRlciA+IDBdIDwtICJwb3MiCiAgICBkZiR2YWRlcl9iaW5hcnlbZGYkdmFkZXIgPD0gMF0gPC0gIm5lZyIKICAgICAgICAKICAgIGRmJHJhdGluZ19iaW5hcnlbZGYkcmF0aW5nID49IDNdIDwtICJwb3MiCiAgICBkZiRyYXRpbmdfYmluYXJ5W2RmJHJhdGluZyA8IDNdIDwtICJuZWciCiAgICBpZih0eXBlID09ICIwMSIpewogICAgICBkZltkZiA9PSAicG9zIl0gPC0gMQogICAgICBkZltkZiA9PSAibmVnIl0gPC0gMAogICAgfQogIH0KICByZXR1cm4oZGF0YWZyYW1lcykKfQoKCnggPC0gZ2V0X2JpbmFyeShsaXN0KHJldmlld3Nfc2VudGltZW50LCB0d2l0dGVyX3NlbnRpbWVudCwgcGFybHZvdGVfc2VudGltZW50KSwgIjAxIikKYGBgCgpgYGB7cn0KcmV2aWV3c19zZW50aW1lbnQkYWZpbm5fYmluYXJ5W3Jldmlld3Nfc2VudGltZW50JGFmaW5uID4gMF0gPC0gIjEiCnJldmlld3Nfc2VudGltZW50JGFmaW5uX2JpbmFyeVtyZXZpZXdzX3NlbnRpbWVudCRhZmlubiA8PSAwXSA8LSAiMCIKcmV2aWV3c19zZW50aW1lbnQkbHNkX2JpbmFyeVtyZXZpZXdzX3NlbnRpbWVudCRsc2QgPiAwXSA8LSAiMSIKcmV2aWV3c19zZW50aW1lbnQkbHNkX2JpbmFyeVtyZXZpZXdzX3NlbnRpbWVudCRsc2QgPD0gMF0gPC0gIjAiCnJldmlld3Nfc2VudGltZW50JHZhZGVyX2JpbmFyeVtyZXZpZXdzX3NlbnRpbWVudCR2YWRlciA+IDBdIDwtICIxIgpyZXZpZXdzX3NlbnRpbWVudCR2YWRlcl9iaW5hcnlbcmV2aWV3c19zZW50aW1lbnQkdmFkZXIgPD0gMF0gPC0gIjAiCnJldmlld3Nfc2VudGltZW50JHJhdGluZ19iaW5hcnlbcmV2aWV3c19zZW50aW1lbnQkcmF0aW5nID49IDNdIDwtICIxIgpyZXZpZXdzX3NlbnRpbWVudCRyYXRpbmdfYmluYXJ5W3Jldmlld3Nfc2VudGltZW50JHJhdGluZyA8IDNdIDwtICIwIgoKdHdpdHRlcl9zZW50aW1lbnQkYWZpbm5fYmluYXJ5W3R3aXR0ZXJfc2VudGltZW50JGFmaW5uID4gMF0gPC0gIjEiCnR3aXR0ZXJfc2VudGltZW50JGFmaW5uX2JpbmFyeVt0d2l0dGVyX3NlbnRpbWVudCRhZmlubiA8PSAwXSA8LSAiMCIKdHdpdHRlcl9zZW50aW1lbnQkbHNkX2JpbmFyeVt0d2l0dGVyX3NlbnRpbWVudCRsc2QgPiAwXSA8LSAiMSIKdHdpdHRlcl9zZW50aW1lbnQkbHNkX2JpbmFyeVt0d2l0dGVyX3NlbnRpbWVudCRsc2QgPD0gMF0gPC0gIjAiCnR3aXR0ZXJfc2VudGltZW50JHZhZGVyX2JpbmFyeVt0d2l0dGVyX3NlbnRpbWVudCR2YWRlciA+IDBdIDwtICIxIgp0d2l0dGVyX3NlbnRpbWVudCR2YWRlcl9iaW5hcnlbdHdpdHRlcl9zZW50aW1lbnQkdmFkZXIgPD0gMF0gPC0gIjAiCnR3aXR0ZXJfc2VudGltZW50JHJhdGluZ19iaW5hcnlbdHdpdHRlcl9zZW50aW1lbnQkcmF0aW5nID09ICJQb3NpdGl2ZSJdIDwtICIxIgp0d2l0dGVyX3NlbnRpbWVudCRyYXRpbmdfYmluYXJ5W3R3aXR0ZXJfc2VudGltZW50JHJhdGluZyA9PSAiTmVnYXRpdmUiXSA8LSAiMCIKdHdpdHRlcl9zZW50aW1lbnQkcmF0aW5nX2JpbmFyeVt0d2l0dGVyX3NlbnRpbWVudCRyYXRpbmcgPT0gIk5ldXRyYWwiXSA8LSBOQQoKcGFybHZvdGVfc2VudGltZW50JGFmaW5uX2JpbmFyeVtwYXJsdm90ZV9zZW50aW1lbnQkYWZpbm4gPiAwXSA8LSAiMSIKcGFybHZvdGVfc2VudGltZW50JGFmaW5uX2JpbmFyeVtwYXJsdm90ZV9zZW50aW1lbnQkYWZpbm4gPD0gMF0gPC0gIjAiCnBhcmx2b3RlX3NlbnRpbWVudCRsc2RfYmluYXJ5W3Bhcmx2b3RlX3NlbnRpbWVudCRsc2QgPiAwXSA8LSAiMSIKcGFybHZvdGVfc2VudGltZW50JGxzZF9iaW5hcnlbcGFybHZvdGVfc2VudGltZW50JGxzZCA8PSAwXSA8LSAiMCIKcGFybHZvdGVfc2VudGltZW50JHZhZGVyX2JpbmFyeVtwYXJsdm90ZV9zZW50aW1lbnQkdmFkZXIgPiAwXSA8LSAiMSIKcGFybHZvdGVfc2VudGltZW50JHZhZGVyX2JpbmFyeVtwYXJsdm90ZV9zZW50aW1lbnQkdmFkZXIgPD0gMF0gPC0gIjAiCgojIG9wdGlvbmFsbHk6IGNvbnZlcnQgMCA9IG5lZ2F0aXZlLCAxID0gcG9zaXRpdmUKI3Jldmlld3NfNTBbcmV2aWV3c181MCA9PSAicG9zIl0gPC0gMQojcmV2aWV3c181MFtyZXZpZXdzXzUwID09ICJuZWciXSA8LSAwCmBgYAoKYGBge3J9CnBhcmx2b3RlX3NlbnRpbWVudApgYGAKCgpgYGB7cn0KZ2V0X3N0YXRpc3RpY3MgPC0gZnVuY3Rpb24oZGYpIHsKICAjc3RhdGlzdGljcyA8LSBkYXRhLmZyYW1lKG1hdHJpeChuY29sPTQsIG5yb3c9MCkpCiAgI3ggPC0gYygiYWNjdXJhY3kiLCAicHJlY2lzaW9uIiwgInJlY2FsbCIsICJGMSIpCiAgI2NvbG5hbWVzKHN0YXRpc3RpY3MpIDwtIHgKICBzdGF0aXN0aWNzIDwtIGRhdGEuZnJhbWUobWF0cml4KG5jb2w9MSwgbnJvdz0wKSkKICBjb2xuYW1lcyhzdGF0aXN0aWNzKSA8LSAiYWNjdXJhY3kiCiAgCiAgbGV4MSA8LSAiYWZpbm5fYmluYXJ5IgogIGxleDIgPC0gImxzZF9iaW5hcnkiCiAgbGV4MyA8LSAidmFkZXJfYmluYXJ5IgogIGdvbGQgPC0gInJhdGluZ19iaW5hcnkiCiAgCiAgbGV4aWNvbnMgPC0gYyhsZXgxLGxleDIsbGV4MykKICAKICBmb3IobGV4IGluIGxleGljb25zKXsKICAgIGNmIDwtIGNvbmZ1c2lvbk1hdHJpeChmYWN0b3IoZGZbW2xleF1dKSxmYWN0b3IoZGZbW2dvbGRdXSkpCiAgICBhY2N1cmFjeSA8LSBjZltbM11dWzFdCiAgICAjY29uZnVzaW9uX21hdHJpeCA8LSB0YWJsZShBQ1RVQUw9ZGZbW2dvbGRdXSwgUFJFRElDVEVEPWRmW1tsZXhdXSkKICAgICNUTiA8LSBjb25mdXNpb25fbWF0cml4WzFdCiAgICAjRk4gPC0gY29uZnVzaW9uX21hdHJpeFsyXQogICAgI0ZQIDwtIGNvbmZ1c2lvbl9tYXRyaXhbM10KICAgICNUUCA8LSBjb25mdXNpb25fbWF0cml4WzRdCiAgICAKICAgICMgY2FsY3VsYXRlIHN0YXRpc3RpY3MKICAgICNwcmVjaXNpb24gPC0gVFAvKFRQK0ZQKQogICAgI2FjY3VyYWN5IDwtIChUUCtUTikvKFRQK1ROK0ZQK0ZOKQogICAgI3JlY2FsbCA8LSBUUC8oVFArRk4pCiAgICAjRjEgPC0gKDIqcHJlY2lzaW9uKnJlY2FsbCkvKHByZWNpc2lvbityZWNhbGwpCiAgCiAgICAjIGFkZCB0byB0YWJsZQogICAgI291dHB1dCA8LSBjKGFjY3VyYWN5LHByZWNpc2lvbiwgcmVjYWxsLCBGMSkKICAgICNzdGF0aXN0aWNzW2xleCxdID0gcmJpbmQoc3RhdGlzdGljc1tbbGV4XV0sIG91dHB1dCkKICAgIHN0YXRpc3RpY3NbbGV4LF0gPSByYmluZChzdGF0aXN0aWNzW1tsZXhdXSwgYWNjdXJhY3kpCiAgICAjcHJpbnQob3V0cHV0KQogICAgfQogICAgCiAgcmV0dXJuKHN0YXRpc3RpY3MpCiAgCn0KCnJldmlld19zdGF0cyA8LSBnZXRfc3RhdGlzdGljcyhyZXZpZXdzX3NlbnRpbWVudCkKI3R3aXR0ZXJfc3RhdHMgPC0gZ2V0X3N0YXRpc3RpY3ModHdpdHRlcl9zZW50aW1lbnQpCiNwYXJsdm90ZV9zdGF0cyA8LSBnZXRfc3RhdGlzdGljcyhwYXJsdm90ZV9zZW50aW1lbnQpCgpyZXZpZXdfc3RhdHMKYGBgCgojIDQuIFN0ZXA6IFBsb3QgRGF0YSAKYGBge3J9CiMgcGxvdCBjb2x1bW5zCnJldmlld3NfZGZtIDwtIG1lbHQoaGVhZChyZXZpZXdzX3NlbnRpbWVudCw1MClbLGMoJ2lkJywnYWZpbm4nLCdsc2QnLCd2YWRlcicpXSxpZC52YXJzID0gMSkKCnJldmlld3NfcGxvdCA8LSBnZ3Bsb3QocmV2aWV3c19kZm0sYWVzKHggPSBpZCx5ID0gdmFsdWUpKSArIAogICAgICAgICAgICAgICAgZ2VvbV9iYXIoYWVzKGZpbGwgPSB2YXJpYWJsZSksc3RhdCA9ICJpZGVudGl0eSIscG9zaXRpb24gPSAiZG9kZ2UiKSArCiAgICAgICAgICAgICAgICBnZ3RpdGxlKCJSZXZpZXdzIFNlbnRpbWVudHMiKQoKCnR3aXR0ZXJfZGZtIDwtIG1lbHQoaGVhZCh0d2l0dGVyX3NlbnRpbWVudCw1MClbLGMoJ2lkJywnYWZpbm4nLCdsc2QnLCd2YWRlcicpXSxpZC52YXJzID0gMSkKCnR3aXR0ZXJfcGxvdCA8LSBnZ3Bsb3QodHdpdHRlcl9kZm0sYWVzKHggPSBpZCx5ID0gdmFsdWUpKSArIAogICAgICAgICAgICAgICAgZ2VvbV9iYXIoYWVzKGZpbGwgPSB2YXJpYWJsZSksc3RhdCA9ICJpZGVudGl0eSIscG9zaXRpb24gPSAiZG9kZ2UiKSArCiAgICAgICAgICAgICAgICBnZ3RpdGxlKCJUd2l0dGVyIFNlbnRpbWVudHMiKQoKcGFybHZvdGVfZGZtIDwtIG1lbHQoaGVhZChwYXJsdm90ZV9zZW50aW1lbnQsNTApWyxjKCdpZCcsJ2FmaW5uJywnbHNkJywndmFkZXInKV0saWQudmFycyA9IDEpCgpwYXJsdm90ZV9wbG90IDwtIGdncGxvdChwYXJsdm90ZV9kZm0sYWVzKHggPSBpZCx5ID0gdmFsdWUpKSArIAogICAgICAgICAgICAgICAgZ2VvbV9iYXIoYWVzKGZpbGwgPSB2YXJpYWJsZSksc3RhdCA9ICJpZGVudGl0eSIscG9zaXRpb24gPSAiZG9kZ2UiKSsKICAgICAgICAgICAgICAgIGZhY2V0X3dyYXAofiB2YXJpYWJsZSwgbmNvbCA9IDEsIHNjYWxlcz0iZnJlZV95IikrCiAgICAgICAgICAgICAgICBnZ3RpdGxlKCJQYXJsVm90ZSBTZW50aW1lbnRzIikKCnBhcmx2b3RlX25vcm1fZGZtIDwtIG1lbHQoaGVhZChwYXJsdm90ZV9zZW50aW1lbnQsNTApWyxjKCdpZCcsJ2FmaW5uX25vcm0nLCdsc2Rfbm9ybScsJ3ZhZGVyX25vcm0nKV0saWQudmFycyA9IDEpCgpwYXJsdm90ZV9ub3JtX3Bsb3QgPC0gZ2dwbG90KHBhcmx2b3RlX25vcm1fZGZtLGFlcyh4ID0gaWQseSA9IHZhbHVlKSkgKyAKICAgICAgICAgICAgICAgIGdlb21fYmFyKGFlcyhmaWxsID0gdmFyaWFibGUpLHN0YXQgPSAiaWRlbnRpdHkiLHBvc2l0aW9uID0gImRvZGdlIikrCiAgICAgICAgICAgICAgICBmYWNldF93cmFwKH4gdmFyaWFibGUsIG5jb2wgPSAxLCBzY2FsZXM9ImZyZWVfeSIpKwogICAgICAgICAgICAgICAgZ2d0aXRsZSgiUGFybFZvdGUgU2VudGltZW50cyIpCgoKcmV2aWV3c19wbG90CnR3aXR0ZXJfcGxvdApwYXJsdm90ZV9wbG90CnBhcmx2b3RlX25vcm1fcGxvdApgYGAKIyMjIEFERElUSU9OQUwKIyBIYXJyeSBQb3R0ZXIgLSBEYXRhc2V0CmBgYHtyfQojIGxvYWQgaGFycnkgcG90dGVyIGRhdGFzZXQgCnRpdGxlcyA8LSBjKCJQaGlsb3NvcGhlcidzIFN0b25lIiwgIkNoYW1iZXIgb2YgU2VjcmV0cyIsICJQcmlzb25lciBvZiBBemthYmFuIiwKICAgICAgICAgICAgIkdvYmxldCBvZiBGaXJlIiwgIk9yZGVyIG9mIHRoZSBQaG9lbml4IiwgIkhhbGYtQmxvb2QgUHJpbmNlIiwKICAgICAgICAgICAgIkRlYXRobHkgSGFsbG93cyIpCgpib29rcyA8LSBsaXN0KHBoaWxvc29waGVyc19zdG9uZSwgY2hhbWJlcl9vZl9zZWNyZXRzLCBwcmlzb25lcl9vZl9hemthYmFuLAogICAgICAgICAgIGdvYmxldF9vZl9maXJlLCBvcmRlcl9vZl90aGVfcGhvZW5peCwgaGFsZl9ibG9vZF9wcmluY2UsCiAgICAgICAgICAgZGVhdGhseV9oYWxsb3dzKQogIApzZXJpZXMgPC0gdGliYmxlKCkKCmZvcihpIGluIHNlcV9hbG9uZyh0aXRsZXMpKSB7CiAgICAgICAgCiAgICAgICAgY2xlYW4gPC0gdGliYmxlKGNoYXB0ZXIgPSBzZXFfYWxvbmcoYm9va3NbW2ldXSksCiAgICAgICAgICAgICAgICAgICAgICAgIHRleHQgPSBib29rc1tbaV1dKSAlPiUKICAgICAgICAgICAgICN1bm5lc3RfdG9rZW5zKHdvcmQsIHRleHQpICU+JQogICAgICAgICAgICAgbXV0YXRlKGJvb2sgPSB0aXRsZXNbaV0pICU+JQogICAgICAgICAgICAgc2VsZWN0KGJvb2ssIGV2ZXJ5dGhpbmcoKSkKCiAgICAgICAgc2VyaWVzIDwtIHJiaW5kKHNlcmllcywgY2xlYW4pCn0KCnNlcmllcyRib29rIDwtIGZhY3RvcihzZXJpZXMkYm9vaywgbGV2ZWxzID0gcmV2KHRpdGxlcykpCgpzZXJpZXMKI2Jvb2tfZ3JvdXBzIDwtIHNlcmllcyAlPiUgZ3JvdXBfYnkoYm9vaywgY2hhcHRlcikKIyB0b2tlbml6ZSBocDEKI2hwMV90b2tlbml6ZWQgPC0gdG9rZW5zX3RvbG93ZXIodG9rZW5zKHBoaWxvc29waGVyc19zdG9uZSwgcmVtb3ZlX3B1bmN0ID0gVFJVRSkpIApgYGAKIyMjIEhhcnJ5IFBvdHRlciAtIEFGSU5OIExleGljb24KYGBge3J9CmFmaW5uX2hwMiA8LSBzZXJpZXMgJT4lCiAgICAgICAgZ3JvdXBfYnkoYm9vaywgY2hhcHRlcikgJT4lICMgYWRkIHdvcmQgZm9yIHNpbmdsZSB3b3JkIHNjb3JlcyAKICAgICAgICBpbm5lcl9qb2luKGdldF9zZW50aW1lbnRzKCJhZmlubiIpKSAlPiUKICAgICAgICBncm91cF9ieShib29rLCBjaGFwdGVyKSAlPiUgIyBhZGQgd29yZCBmb3Igc2luZ2xlIHdvcmQgc2NvcmVzCiAgICAgICAgI3N1bW1hcmlzZShzZW50aW1lbnQgPSBzdW0odmFsdWUpKSAlPiUKICAgICAgICBzdW1tYXJpc2Uoc2VudGltZW50ID0gbWVhbih2YWx1ZSwgbmEucm0gPSBUUlVFKSkgJT4lCiAgICAgICAgbXV0YXRlKG1ldGhvZCA9ICJBRklOTiIpICAlPiUKICAgICAgICBnZ3Bsb3QoYWVzKGNoYXB0ZXIsIHNlbnRpbWVudCwgZmlsbCA9IGJvb2spKSArCiAgICAgICAgICBnZW9tX2JhcihhbHBoYSA9IDAuOCwgc3RhdCA9ICJpZGVudGl0eSIsIHNob3cubGVnZW5kID0gRkFMU0UpICsKICAgICAgICAgIGZhY2V0X3dyYXAofiBib29rLCBuY29sID0gMiwgc2NhbGVzID0gImZyZWVfeCIpCgphZmlubl9ocDIKCiNnZ3NhdmUocGxvdCA9IGFmaW5uLCB3aWR0aCA9IDE1LCBoZWlnaHQgPSAxNSwgZHBpID0gMzAwLCBmaWxlbmFtZSA9ICJhZmlubl9ocF9tZWFuLnBuZyIpCmBgYAoKIyBMZXhpY29kZXI6IEhQCmBgYHtyfQojIHNlbGVjdCBvbmx5IHRoZSAibmVnYXRpdmUiIGFuZCAicG9zaXRpdmUiIGNhdGVnb3JpZXMKI2RhdGFfZGljdGlvbmFyeV9MU0QyMDE1X3Bvc19uZWcgPC0gZGF0YV9kaWN0aW9uYXJ5X0xTRDIwMTVbMToyXQojaHAxX2xzZCA8LSB0b2tlbnNfbG9va3VwKGhwMV90b2tlbml6ZWQsIGRpY3Rpb25hcnkgPSBkYXRhX2RpY3Rpb25hcnlfTFNEMjAxNV9wb3NfbmVnKQoKcG9sYXJpdHkoZGF0YV9kaWN0aW9uYXJ5X0xTRDIwMTUpIDwtIAogIGxpc3QocG9zID0gYygicG9zaXRpdmUiLCAibmVnX25lZ2F0aXZlIiksIG5lZyA9IGMoIm5lZ2F0aXZlIiwgIm5lZ19wb3NpdGl2ZSIpKQoKaHAxX2xzZCA8LSB0ZXh0c3RhdF9wb2xhcml0eShocDFfdG9rZW5pemVkLCBkYXRhX2RpY3Rpb25hcnlfTFNEMjAxNSkKCmhwMV9sc2RfdG9rZW5zIDwtIHRva2Vuc19sb29rdXAoaHAxX3Rva2VuaXplZCwgZGF0YV9kaWN0aW9uYXJ5X0xTRDIwMTUsIG5lc3RlZF9zY29wZSA9ICJkaWN0aW9uYXJ5IiwgZXhjbHVzaXZlID0gRkFMU0UpCmhwMV9sc2QuZGYgPC0gYXMuZGF0YS5mcmFtZS5tYXRyaXgoaHAxX2xzZCkKaHAxX2xzZC5kZiRjaGFwdGVyIDwtIDE6bnJvdyhocDFfbHNkLmRmKQoKcGxvdCA8LSBnZ3Bsb3QoaHAxX2xzZCwgYWVzKHggPWhwMV9sc2QuZGYkY2hhcHRlciwgeT1zZW50aW1lbnQpKSArCiAgICAgICAgICBnZW9tX2JhcihhbHBoYSA9IDAuOCwgc3RhdCA9ICJpZGVudGl0eSIsIHNob3cubGVnZW5kID0gRkFMU0UpCnBsb3QgKyB5bGltKC0xLjAsIDEuMCkgKyBsYWJzKHk9InNlbnRpbWVudCIsIHggPSAiY2hhcHRlciIpICsgZ2d0aXRsZSgiSFAxIC0gTGV4aWNvZGVyIikKYGBgCiMgQUZJTk46IEhQCmBgYHtyfQpocDFfYWZpbm4gPC0gdGV4dHN0YXRfdmFsZW5jZShocDFfdG9rZW5pemVkLCBhZmlubiwgbm9ybWFsaXplPSJkaWN0aW9uYXJ5IikKCmhwMV9hZmlubi5kZiA8LSBhcy5kYXRhLmZyYW1lLm1hdHJpeChocDFfYWZpbm4pCmhwMV9hZmlubi5kZiRjaGFwdGVyIDwtIDE6bnJvdyhocDFfYWZpbm4uZGYpCgpwbG90IDwtIGdncGxvdChocDFfYWZpbm4uZGYsIGFlcyh4ID1ocDFfYWZpbm4uZGYkY2hhcHRlciwgeT1zZW50aW1lbnQpKSArCiAgICAgICAgICBnZW9tX2JhcihhbHBoYSA9IDAuOCwgc3RhdCA9ICJpZGVudGl0eSIsIHNob3cubGVnZW5kID0gRkFMU0UpCnBsb3QgKyB5bGltKC0xLjAsIDEuMCkgKyBsYWJzKHk9InNlbnRpbWVudCIsIHggPSAiY2hhcHRlciIpICsgZ2d0aXRsZSgiSFAxIC0gQUZJTk4iKQpgYGAKIyBWQURFUjogSFAKYGBge3J9CmdldF92YWRlcihwaGlsb3NvcGhlcnNfc3RvbmVbMV0pCgpocDFfdmFkZXIgPC0gdmFkZXJfZGYocGhpbG9zb3BoZXJzX3N0b25lKQpocDFfdmFkZXIkY2hhcHRlciA8LSAxOm5yb3coaHAxX3ZhZGVyKQoKcGxvdCA8LSBnZ3Bsb3QoaHAxX3ZhZGVyLCBhZXMoeCA9Y2hhcHRlciwgeT1jb21wb3VuZCkpICsKICAgICAgICAgIGdlb21fYmFyKGFscGhhID0gMC44LCBzdGF0ID0gImlkZW50aXR5Iiwgc2hvdy5sZWdlbmQgPSBGQUxTRSkKcGxvdCArIHlsaW0oLTUuMCwgNS4wKSArIGxhYnMoeT0ic2VudGltZW50IiwgeCA9ICJjaGFwdGVyIikgKyBnZ3RpdGxlKCJIUDEgLSBWQURFUiIpCmBgYAojIFFVQU5URURBLlNFTlRJTUVOVAojIEFGSU5OOiBIUApgYGB7cn0KIyBXb3JrIHdpdGggcXVhbnRlZGEuc2VudGltZW50IG9uIEhQIGNvcnB1czoKIyBjb252ZXJ0IHRpYmJsZSB0byBkYXRhZnJhbWUKc2VyaWVzLmRmIDwtIGFzLmRhdGEuZnJhbWUoc2VyaWVzKQoKIyB0b2tlbml6ZSBib29rcwpzZXJpZXNfdG9rZW5pemVkIDwtIHNlcmllcy5kZiAlPiUKICB1bm5lc3RfdG9rZW5zKHRva2VucywgdGV4dCkKCiMgYXBwbHkgYWZpbm4gbGV4aWNvbgpzZXJpZXNfdG9rZW5pemVkJGFmaW5uIDwtIHRleHRzdGF0X3ZhbGVuY2Uoc2VyaWVzX3Rva2VuaXplZCR0b2tlbnMsIGFmaW5uKSRzZW50aW1lbnQKCiMgcmVwbGFjZSBhbGwgMCB2YWx1ZXMgd2l0aCBuYQpzZXJpZXNfdG9rZW5pemVkW3Nlcmllc190b2tlbml6ZWQgPT0gMF0gPC0gTkEKCnNlcmllc190b2tlbml6ZWQgJT4lCiAgZ3JvdXBfYnkoYm9vaywgY2hhcHRlcikgJT4lICMgZ3JvdXAgZGYgYnkgYm9vayBhbmQgY2hhcHRlciB0byBnZXQgc2VudGltZW50IHBlciBjaGFwdGVyCiAgc3VtbWFyaXNlKHNlbnRpbWVudCA9IG1lYW4oYWZpbm4sIG5hLnJtID0gVFJVRSkpICU+JSAjIGNhbGN1bGF0ZSBtZWFuIHcvbyByZWdhcmRpbmcgbmEgdmFsdWVzCiAgbXV0YXRlKG1ldGhvZCA9ICJBRklOTiIpICU+JSAjIGFkZCBjb2x1bW4gd2l0aCBtZXRob2QgCiAgICAgICAgZ2dwbG90KGFlcyhjaGFwdGVyLCBzZW50aW1lbnQsIGZpbGwgPSBib29rKSkgKyAjIHBsb3Qgc2VudGltZW50IG9mIGJvb2tzCiAgICAgICAgICBnZW9tX2JhcihhbHBoYSA9IDAuOCwgc3RhdCA9ICJpZGVudGl0eSIsIHNob3cubGVnZW5kID0gRkFMU0UpICsKICAgICAgICAgIGZhY2V0X3dyYXAofiBib29rLCBuY29sID0gMiwgc2NhbGVzID0gImZyZWVfeCIpICsKICAgICAgICAgIGdndGl0bGUoIkFGSU5OIEhQIikKYGBgCiMgTGV4aWNvZGVyOiBIUCAgCmBgYHtyfQojIFdvcmsgd2l0aCBxdWFudGVkYS5zZW50aW1lbnQgb24gSFAgY29ycHVzOgojIGFwcGx5IGxleGljb2RlciBsZXhpY29uCnNlcmllcyRsc2QgPC0gdGV4dHN0YXRfcG9sYXJpdHkodG9rZW5zKHNlcmllcyR0ZXh0KSwgZGF0YV9kaWN0aW9uYXJ5X0xTRDIwMTUpJHNlbnRpbWVudCAKCiNzZXJpZXMuZGYgPC0gYXMuZGF0YS5mcmFtZShzZXJpZXMpCgpwbG90IDwtIGdncGxvdChzZXJpZXMsIGFlcyhjaGFwdGVyLCBsc2QsIGZpbGwgPSBib29rKSkgKyAjIHBsb3Qgc2VudGltZW50IG9mIGJvb2tzCiAgICAgICAgICBnZW9tX2JhcihhbHBoYSA9IDAuOCwgc3RhdCA9ICJpZGVudGl0eSIsIHNob3cubGVnZW5kID0gRkFMU0UpICsKICAgICAgICAgIGZhY2V0X3dyYXAofiBib29rLCBuY29sID0gMiwgc2NhbGVzID0gImZyZWVfeCIpICsKICAgICAgICAgIGdndGl0bGUoIkxleGljb2RlciBIUCIpCnBsb3QgCmBgYAoKIyBWYWRlcjogSFAKYGBge3J9CiMgYXBwbHkgdmFkZXIgbGV4aWNvbiB0byBhbGwgSFAgYm9va3MKc2VyaWVzJHZhZGVyIDwtIHZhZGVyX2RmKHNlcmllcyR0ZXh0KSRjb21wb3VuZAoKI3Nlcmllcy5kZiA8LSBhcy5kYXRhLmZyYW1lKHNlcmllcykKCnBsb3QgPC0gZ2dwbG90KHNlcmllcywgYWVzKGNoYXB0ZXIsIGxzZCwgZmlsbCA9IGJvb2spKSArICMgcGxvdCBzZW50aW1lbnQgb2YgYm9va3MKICAgICAgICAgIGdlb21fYmFyKGFscGhhID0gMC44LCBzdGF0ID0gImlkZW50aXR5Iiwgc2hvdy5sZWdlbmQgPSBGQUxTRSkgKwogICAgICAgICAgZmFjZXRfd3JhcCh+IGJvb2ssIG5jb2wgPSAyLCBzY2FsZXMgPSAiZnJlZV94IikgKwogICAgICAgICAgZ2d0aXRsZSgiVkFERVIgSFAiKQpwbG90IApgYGAKCiMgUkVWSUVXUyBEQVRBU0VUCmBgYHtyfQojIGxvYWQgZGF0YXNldApyZXZpZXdzIDwtIHJlYWR0ZXh0KCJkYXRhc2V0cy9nb29kcmVhZHNfcmV2aWV3c19jaGlsZHJlbl8yLmpzb24iLCB0ZXh0X2ZpZWxkID0gInJldmlld190ZXh0IikKCiMgY29udmVydCB0byBkYXRhZnJhbWUKcmV2aWV3cy5kZiA8LSBhcy5kYXRhLmZyYW1lKHJldmlld3MpCgojIGFkZCBkb2NfaWQgKGkuZS4gYWNjb3JkaW5nIHRvIGluZGV4KQpyZXZpZXdzLmRmJGRvY19pZCA8LSAxOm5yb3cocmV2aWV3cy5kZikKYGBgCgojIyMgU2FtcGxlIERhdGFzZXQKYGBge3J9CiMgZ2V0IHJhbmRvbSBzYW1wbGUgb2YgNTAgcmV2aWV3cyAKcmV2aWV3c19zYW1wbGUgPC0gcmV2aWV3cy5kZltzYW1wbGUoMTpucm93KHJldmlld3MuZGYpLCA1MCwKICAgcmVwbGFjZT1GQUxTRSksXQoKIyBnZXQgZmlyc3QgNTAgcm93cyBvZiBkYXRhIApyZXZpZXdzXzUwIDwtIGhlYWQocmV2aWV3cy5kZiw1MCkKcmV2aWV3c181MCA9IHN1YnNldChyZXZpZXdzXzUwLCBzZWxlY3QgPSBjKGRvY19pZCx0ZXh0LHJhdGluZykpCmBgYAojIyMgR2V0IFRyYW5zbGF0aW9ucyBvZiBEYXRhc2V0IApgYGB7cn0KIyBlaXRoZXIgdmlhIGNvcnB1cyAKcmV2aWV3cy5jb3JwdXMgPC0gY29ycHVzKHJldmlld3MpCmRvY3ZhcnMocmV2aWV3cy5jb3JwdXMsICJsYW5ndWFnZSIpIDwtIHRleHRjYXQocmV2aWV3cy5jb3JwdXMpCnJldmlld3NfZW4gPC0gY29ycHVzX3N1YnNldChyZXZpZXdzLmNvcnB1cywgbGFuZ3VhZ2UgPT0gImVuZ2xpc2giLCBkcm9wX2RvY2lkID0gVFJVRSkKCiMgb3IgdmlhIGRhdGFmcmFtZSBsb2dpYwpyZXZpZXdzLmRmJGxhbmd1YWdlIDwtIHRleHRjYXQocmV2aWV3cy5kZiR0ZXh0KQpgYGAKCiMjIyBTZW50aW1lbnQgQW5hbHlzaXMgb24gUmV2aWV3cyBEYXRhc2V0CgojIyMjIEFGSU5OCmBgYHtyfQojIEFmaW5uCiMgdG9rZW5pemUgCnJldmlld3NfdG9rZW5pemVkIDwtIHJldmlld3NfNTAgJT4lCiAgdW5uZXN0X3Rva2Vucyh0b2tlbnMsIHRleHQpCgojIGFwcGx5IGFmaW5uIGxleGljb24KcmV2aWV3c190b2tlbml6ZWQkYWZpbm4gPC0gdGV4dHN0YXRfdmFsZW5jZShyZXZpZXdzX3Rva2VuaXplZCR0b2tlbnMsIGFmaW5uKSRzZW50aW1lbnQKCiMgcmVwbGFjZSBhbGwgMCB2YWx1ZXMgd2l0aCBuYQpyZXZpZXdzX3Rva2VuaXplZFtyZXZpZXdzX3Rva2VuaXplZCA9PSAwXSA8LSBOQQoKIyBjYWxjdWxhdGUgbWVhbiBzY29yZXMgZm9yIHRva2VucyBwZXIgZG9jCmFmaW5uX3Njb3JlcyA8LSByZXZpZXdzX3Rva2VuaXplZCAlPiUKICBncm91cF9ieShkb2NfaWQpICU+JSAjIGdyb3VwIGRmIGJ5IGRvY19pZCB0byBnZXQgbWVhbiBzZW50aW1lbnQgc2NvcmUKICBzdW1tYXJpc2UodG90YWwgPSBtZWFuKGFmaW5uLCBuYS5ybSA9IFRSVUUpKSAjJT4lICMgY2FsY3VsYXRlIG1lYW4gdy9vIHJlZ2FyZGluZyBuYSB2YWx1ZXMKCiMgYWRkIGFmaW5uIHNjb3JlcyB0byBkZiAKcmV2aWV3c181MCRhZmlubiA8LSBhZmlubl9zY29yZXMkdG90YWwKCiMgZGlmZmVyZW50IHZlcnNpb24gdG8gZ2V0IHBsb3QgCnJldmlld3NfdG9rZW5pemVkICU+JQogIGdyb3VwX2J5KGRvY19pZCkgJT4lICMgZ3JvdXAgZGYgYnkgYm9vayBhbmQgY2hhcHRlciB0byBnZXQgc2VudGltZW50IHBlciBjaGFwdGVyCiAgI3Jldmlld3NfdG9rZW5pemVkJHNlbnRpbWVudCA9IG1lYW4oYWZpbm4sIG5hLnJtID0gVFJVRSkgJT4lCiAgc3VtbWFyaXNlKHNlbnRpbWVudCA9IG1lYW4oYWZpbm4sIG5hLnJtID0gVFJVRSkpICU+JSAjIGNhbGN1bGF0ZSBtZWFuIHcvbyByZWdhcmRpbmcgbmEgdmFsdWVzCiAgbXV0YXRlKG1ldGhvZCA9ICJBRklOTiIpICU+JSAjIGFkZCBjb2x1bW4gd2l0aCBtZXRob2QgCiAgICAgICAgZ2dwbG90KGFlcyhkb2NfaWQsIHNlbnRpbWVudCwgZmlsbCA9IGRvY19pZCkpICsgIyBwbG90IHNlbnRpbWVudCBvZiBib29rcwogICAgICAgICAgZ2VvbV9iYXIoYWxwaGEgPSAwLjgsIHN0YXQgPSAiaWRlbnRpdHkiLCBzaG93LmxlZ2VuZCA9IEZBTFNFKSArCiAgICAgICAgICAjZmFjZXRfd3JhcCh+IGRvY19pZCwgbmNvbCA9IDIsIHNjYWxlcyA9ICJmcmVlX3giKSArCiAgICAgICAgICBnZ3RpdGxlKCJBRklOTiBSZXZpZXdzIikKYGBgCiMjIyMgTEVYSUNPREVSCmBgYHtyfQojIGFwcGx5IGxleGljb2RlciBsZXhpY29uCnJldmlld3NfNTAkbHNkIDwtIHRleHRzdGF0X3BvbGFyaXR5KHRva2VucyhyZXZpZXdzXzUwJHRleHQpLCBkYXRhX2RpY3Rpb25hcnlfTFNEMjAxNSkkc2VudGltZW50IAojc2VyaWVzLmRmIDwtIGFzLmRhdGEuZnJhbWUoc2VyaWVzKQoKcGxvdCA8LSBnZ3Bsb3QocmV2aWV3c181MCwgYWVzKGRvY19pZCwgbHNkLCBmaWxsID0gZG9jX2lkKSkgKyAjIHBsb3Qgc2VudGltZW50IG9mIGJvb2tzCiAgICAgICAgICBnZW9tX2JhcihhbHBoYSA9IDAuOCwgc3RhdCA9ICJpZGVudGl0eSIsIHNob3cubGVnZW5kID0gRkFMU0UpICsKICAgICAgICAgICNmYWNldF93cmFwKH4gZG9jX2lkLCBuY29sID0gMiwgc2NhbGVzID0gImZyZWVfeCIpICsKICAgICAgICAgIGdndGl0bGUoIkxleGljb2RlciBSZXZpZXdzIikKcGxvdCAKYGBgCiMjIyMgVmFkZXIKYGBge3J9CnJldmlld3NfNTAkdmFkZXIgPC0gdmFkZXJfZGYocmV2aWV3c181MCR0ZXh0KSRjb21wb3VuZAoKcGxvdCA8LSBnZ3Bsb3QocmV2aWV3c181MCwgYWVzKGRvY19pZCwgdmFkZXIsIGZpbGwgPSBkb2NfaWQpKSArICMgcGxvdCBzZW50aW1lbnQgb2YgYm9va3MKICAgICAgICAgIGdlb21fYmFyKGFscGhhID0gMC44LCBzdGF0ID0gImlkZW50aXR5Iiwgc2hvdy5sZWdlbmQgPSBGQUxTRSkgKwogICAgICAgICAgI2ZhY2V0X3dyYXAofiBkb2NfaWQsIG5jb2wgPSAyLCBzY2FsZXMgPSAiZnJlZV94IikgKwogICAgICAgICAgZ2d0aXRsZSgiVmFkZXIgUmV2aWV3cyIpCnBsb3QgCmBgYAojIFN0YXRpc3RpY3MgCmBgYHtyfQojIGNvbnZlcnQgdG8gYmluYXJ5IHJlc3VsdHMKcmV2aWV3c181MCRhZmlubl9iaW5hcnlbcmV2aWV3c181MCRhZmlubiA+IDBdIDwtICJwb3MiCnJldmlld3NfNTAkYWZpbm5fYmluYXJ5W3Jldmlld3NfNTAkYWZpbm4gPD0gMF0gPC0gIm5lZyIKCnJldmlld3NfNTAkbHNkX2JpbmFyeVtyZXZpZXdzXzUwJGxzZCA+IDBdIDwtICJwb3MiCnJldmlld3NfNTAkbHNkX2JpbmFyeVtyZXZpZXdzXzUwJGxzZCA8PSAwXSA8LSAibmVnIgoKcmV2aWV3c181MCR2YWRlcl9iaW5hcnlbcmV2aWV3c181MCR2YWRlciA+IDBdIDwtICJwb3MiCnJldmlld3NfNTAkdmFkZXJfYmluYXJ5W3Jldmlld3NfNTAkdmFkZXIgPD0gMF0gPC0gIm5lZyIKCnJldmlld3NfNTAkcmF0aW5nX2JpbmFyeVtyZXZpZXdzXzUwJHJhdGluZyA+PSAzXSA8LSAicG9zIgpyZXZpZXdzXzUwJHJhdGluZ19iaW5hcnlbcmV2aWV3c181MCRyYXRpbmcgPCAzXSA8LSAibmVnIgoKIyBvcHRpb25hbGx5OiBjb252ZXJ0IDAgPSBuZWdhdGl2ZSwgMSA9IHBvc2l0aXZlCnJldmlld3NfNTBbcmV2aWV3c181MCA9PSAicG9zIl0gPC0gMQpyZXZpZXdzXzUwW3Jldmlld3NfNTAgPT0gIm5lZyJdIDwtIDAKYGBgCgpgYGB7cn0KYWN0dWFsX3ZhbHVlcyA8LSB0d2l0dGVyX3NlbnRpbWVudCRyYXRpbmdfYmluYXJ5CnByZWRpY3RfdmFsdWVzIDwtIHR3aXR0ZXJfc2VudGltZW50JGFmaW5uX2JpbmFyeQoKIyBjcmVhdGUgY29uZnVzaW9uIG1hdHJpeCAKY29uZnVzaW9uX21hdHJpeCA8LSB0YWJsZShBQ1RVQUw9YWN0dWFsX3ZhbHVlcywgUFJFRElDVEVEPXByZWRpY3RfdmFsdWVzKQoKIyBhc3NpZ24gdmFsdWVzIGZyb20gbWF0cml4IHRvIHRydWUvZmFsc2UgcG9zaXRpdmVzL25lZ2F0aXZlcwpUTiA8LSBjb25mdXNpb25fbWF0cml4WzFdCkZOIDwtIGNvbmZ1c2lvbl9tYXRyaXhbMl0KRlAgPC0gY29uZnVzaW9uX21hdHJpeFszXQpUUCA8LSBjb25mdXNpb25fbWF0cml4WzRdCgojIGNhbGN1bGF0ZSBzdGF0aXN0aWNzCnByZWNpc2lvbiA8LSBUUC8oVFArRlApCmFjY3VyYWN5IDwtIChUUCtUTikvKFRQK1ROK0ZQK0ZOKQpyZWNhbGwgPC0gVFAvKFRQK0ZOKQpGMSA8LSAoMipwcmVjaXNpb24qcmVjYWxsKS8ocHJlY2lzaW9uK3JlY2FsbCkKCnByZWNpc2lvbgphY2N1cmFjeQpyZWNhbGwKY29uZnVzaW9uX21hdHJpeFszXQpgYGAKYGBge3J9CnR3aXR0ZXJfc2VudGltZW50W3R3aXR0ZXJfc2VudGltZW50ID09IE5BXSA8LSAwCgp0d2l0dGVyX3NlbnRpbWVudFtpcy5uYSh0d2l0dGVyX3NlbnRpbWVudCldIDwtIDAKCnR3aXR0ZXJfc2VudGltZW50CmBgYAoKYGBge3J9CmdldF9zdGF0aXN0aWNzIDwtIGZ1bmN0aW9uKGRmKSB7CiAgc3RhdGlzdGljcyA8LSBkYXRhLmZyYW1lKG1hdHJpeChuY29sPTQsIG5yb3c9MCkpCiAgeCA8LSBjKCJhY2N1cmFjeSIsICJwcmVjaXNpb24iLCAicmVjYWxsIiwgIkYxIikKICBjb2xuYW1lcyhzdGF0aXN0aWNzKSA8LSB4CiAgbGV4MSA8LSAiYWZpbm5fYmluYXJ5IgogIGxleDIgPC0gImxzZF9iaW5hcnkiCiAgbGV4MyA8LSAidmFkZXJfYmluYXJ5IgogIGdvbGQgPC0gInJhdGluZ19iaW5hcnkiCiAgCiAgbGV4aWNvbnMgPC0gYyhsZXgxLGxleDIsbGV4MykKICAKICBmb3IobGV4IGluIGxleGljb25zKXsKICAgIGNvbmZ1c2lvbl9tYXRyaXggPC0gdGFibGUoQUNUVUFMPWRmW1tnb2xkXV0sIFBSRURJQ1RFRD1kZltbbGV4XV0pCiAgICBUTiA8LSBjb25mdXNpb25fbWF0cml4WzFdCiAgICBGTiA8LSBjb25mdXNpb25fbWF0cml4WzJdCiAgICBGUCA8LSBjb25mdXNpb25fbWF0cml4WzNdCiAgICBUUCA8LSBjb25mdXNpb25fbWF0cml4WzRdCiAgCiAgICAjIGNhbGN1bGF0ZSBzdGF0aXN0aWNzCiAgICBwcmVjaXNpb24gPC0gVFAvKFRQK0ZQKQogICAgYWNjdXJhY3kgPC0gKFRQK1ROKS8oVFArVE4rRlArRk4pCiAgICByZWNhbGwgPC0gVFAvKFRQK0ZOKQogICAgRjEgPC0gKDIqcHJlY2lzaW9uKnJlY2FsbCkvKHByZWNpc2lvbityZWNhbGwpCiAgCiAgICAjIGFkZCB0byB0YWJsZQogICAgb3V0cHV0IDwtIGMoYWNjdXJhY3kscHJlY2lzaW9uLCByZWNhbGwsIEYxKQogICAgc3RhdGlzdGljc1tsZXgsXSA9IHJiaW5kKHN0YXRpc3RpY3NbW2xleF1dLCBvdXRwdXQpCiAgICB9CiAgICAKICByZXR1cm4oc3RhdGlzdGljcykKICAKfQoKZ2V0X3N0YXRpc3RpY3ModGVzdCkKYGBgCgoKCgoKCgo=